package cn.edu.usst.cs.goldfish.booking.meetingroom.service.impl;

import cn.edu.usst.cs.goldfish.booking.meetingroom.config.MeetingRoomsConfig;
import cn.edu.usst.cs.goldfish.booking.meetingroom.handler.GoldfishBookingException;
import cn.edu.usst.cs.goldfish.booking.meetingroom.mapstruct.BookingMapper;
import cn.edu.usst.cs.goldfish.booking.meetingroom.model.*;
import cn.edu.usst.cs.goldfish.booking.meetingroom.mybatis.*;
import cn.edu.usst.cs.goldfish.booking.meetingroom.service.BookingService;
import cn.edu.usst.cs.goldfish.booking.meetingroom.util.SlotUtil;
import cn.edu.usst.cs.goldfish.booking.meetingroom.web.dto.booking.BookingRecordDTO;
import cn.edu.usst.cs.goldfish.booking.meetingroom.web.dto.booking.Slot;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Predicate;

@Service
@Slf4j
@RequiredArgsConstructor
public class BookingServiceImpl implements BookingService {

    private final MeetingSlotMapper slotMapper;
    private final MeetingMapper meetingMapper;
    private final MeetingRoomMapper meetingRoomMapper;
    private final MeetingRoomsConfig meetingRoomsConfig;
    private final UserMapper userMapper;
    private final MeetingAttendeeMapper meetingAttendeeMapper;
    private final KafkaTemplate<String, String> kafkaTemplate;
    private final BookingMapper bookingMapper;
    private final MeetingSlotMapper meetingSlotMapper;

    @Override
    public List<Slot> getOccupiedSlotsOfARoom(Long roomId, LocalDate date) {
        List<MeetingSlot> occupiedMeetingSlots = getOccupiedMeetingSlots(roomId, date);
        return occupiedMeetingSlots.stream()
                .map(bookingMapper::fromMeetingSlot)
                .toList();
    }

    @NotNull
    private List<MeetingSlot> getOccupiedMeetingSlots(Long roomId, LocalDate date) {
        List<Meeting> meetings = meetingMapper.selectList(
                new QueryWrapper<Meeting>()
                        .eq("room_id", roomId)
        );
        List<MeetingSlot> occupiedMeetingSlots = new ArrayList<>();
        for (Meeting meeting : meetings) {
            occupiedMeetingSlots.addAll(meetingSlotMapper.selectList(
                            new QueryWrapper<MeetingSlot>()
                                    .eq("meeting_id", meeting.getId())
                                    .eq("date", date)
                    )
            );
        }
        return occupiedMeetingSlots;
    }

    @Transactional
    @Override
    public void bookARoom(Long roomId, Long userId, LocalDateTime start, LocalDateTime end, String topic) {
        // 写入 meeting 表
        Meeting meeting = new Meeting();
        meeting.setRoomId(roomId);
        meeting.setOrganizerId(userId);
        meeting.setTopic(topic);
        List<MeetingSlot> existingSlots = meetingSlotMapper.selectList(
                new QueryWrapper<>()
        );
        Long maxId = existingSlots
                             .stream()
                             .map(MeetingSlot::getId)
                             .max(Long::compareTo)
                             .orElse(0L) + 1;

        meetingMapper.insert(meeting);
        if (meeting.getId() <= 0) {
            meeting.setId(maxId);
        }

        // 计算时间段内所有 slot
        List<Slot> slots = SlotUtil.computeSlotsBetween(roomId, start, end);

        // 写入 slot 表
        slots.forEach(slot -> {
            slot.setMeetingId(meeting.getId());
            MeetingSlot meetingSlot = bookingMapper.fromSlot(slot);

            slotMapper.insert(meetingSlot);
        });
    }

    @Override
    public void cancelBooking(Long bookingId) {
        slotMapper.deleteByMeetingId(bookingId);
        meetingMapper.deleteById(bookingId);
    }

    @Override
    public List<BookingRecordDTO> getBookingsByUserEmail(String email) {
        // 替换 selectByEmail
        SysUser user = userMapper.selectOne(new QueryWrapper<SysUser>().eq("email", email));
        Long organizerId = user != null ? user.getId() : null;
        List<Meeting> meetings = meetingMapper.selectByOrganizerId(organizerId);
        List<BookingRecordDTO> result = new ArrayList<>();
        for (Meeting m : meetings) {
            BookingRecordDTO dto = new BookingRecordDTO();
            dto.setId(m.getId());
            dto.setTopic(m.getTopic());
            dto.setRoomName(meetingRoomMapper.selectById(m.getRoomId()).getRoomName());
            List<MeetingSlot> slots = slotMapper.selectByMeetingId(m.getId());
            if (!slots.isEmpty()) {
                // 直接用MeetingSlot的date字段（已为LocalDateTime）
                List<java.time.LocalDateTime> times = new ArrayList<>();
                for (MeetingSlot slot : slots) {
                    // 合成LocalDateTime，修复类型不兼容
                    times.add(java.time.LocalDateTime.of(
                            slot.getDate(),
                            java.time.LocalTime.of(slot.getHour(), (slot.getPart() - 1) * 10)));
                }
                java.time.LocalDateTime min = times.stream().min(java.time.LocalDateTime::compareTo).orElse(null);
                java.time.LocalDateTime max = times.stream().max(java.time.LocalDateTime::compareTo).orElse(null);
                dto.setStartTime(min);
                // 结束时间为最后一个slot的结束（+10分钟）
                if (max != null) {
                    max = max.plusMinutes(10);
                }
                dto.setEndTime(max);
            }
            dto.setStatus(m.getStatus());
            result.add(dto);
        }
        return result;
    }

    @Override
    public void scanAndNotifyMeetings() {
        log.warn("Scanning and notifying meetings...");
        // 1. 标记已结束会议为completed
        List<Meeting> expired = meetingMapper.selectExpiredUncompletedMeetings();
        if (expired.isEmpty()) {
            log.info("No expired meetings to update.");
        }
        for (Meeting m : expired) {
            log.info("Updating meeting status to completed: {}", m.getId());
            m.setStatus(cn.edu.usst.cs.goldfish.booking.meetingroom.model.enums.MeetingStatus.completed);
            meetingMapper.updateMeeting(
                    m.getStatus(),
                    m.getRoomId(),
                    m.getOrganizerId(),
                    m.getTopic(),
                    m.getPrice(),
                    m.getId());
        }
        // 2. 查找24小时内即将开始的会议，发送Kafka消息
        List<Meeting> upcoming = meetingMapper.selectMeetingsWithin24Hours();

        for (Meeting m : upcoming) {

            List<MeetingAttendee> attendees = meetingAttendeeMapper.selectByMeetingId(m.getId());
            log.warn("Upcoming Meeting {} \n attendees: {}", m, attendees);
            // 查询所有slot，拼接时间段
            List<MeetingSlot> slots = slotMapper.selectByMeetingId(m.getId());
            if (slots.isEmpty())
                continue;
            // 计算最早和最晚slot
            java.util.List<LocalDateTime> times = new java.util.ArrayList<>();
            for (MeetingSlot slot : slots) {
                int minute = (slot.getPart() - 1) * 10;
                LocalTime translatedTime = LocalTime.of(
                        slot.getHour(),
                        minute);
                log.info("for slot {} time is translated to {}", slot, translatedTime);

                times.add(LocalDateTime.of(
                        slot.getDate(),
                        translatedTime));
            }
            LocalDateTime min = times.stream().min(LocalDateTime::compareTo).orElse(null);
            LocalDateTime max = times.stream().max(LocalDateTime::compareTo).orElse(null);
            String timeStr = (min != null && max != null)
                    ? String.format("%s %02d:%02d ~ %02d:%02d", min.toLocalDate(), min.getHour(), min.getMinute(),
                    max.getHour(), max.getMinute() + 10)
                    : "未知";
            for (MeetingAttendee attendee : attendees) {
                SysUser user = userMapper.selectById(attendee.getUserId());
                String msg = String.format("会议提醒：\n主题：%s\n时间：%s\n参会人：%s",
                        m.getTopic(),
                        timeStr,
                        user != null ? user.getEmail() : attendee.getUserId());
                log.warn("Sending Kafka message: {}", msg);
                kafkaTemplate.send("meeting-reminder", m.getId() + ":" + attendee.getUserId(), msg);
            }
        }
    }

    @Override
    public List<cn.edu.usst.cs.goldfish.booking.meetingroom.model.Meeting> getAllMeetings() {
        return meetingMapper.selectList(new QueryWrapper<>());
    }

    @Override
    public List<Slot> getFreeSlotsOfARoomPerDay(Long roomId, LocalDate date) {
        List<MeetingSlot> rawOccupiedSlots = getOccupiedMeetingSlots(roomId, date);
        log.warn("房号 {} 在 {} 已占用: {}", roomId, date, rawOccupiedSlots);
        List<Slot> freeSlots = new ArrayList<>();
        for (short hour = meetingRoomsConfig.getStartHour(); hour < meetingRoomsConfig.getEndHour(); hour++) {
            for (short part = 1; part <= 6; part++) {
                short finalHour = hour;
                short finalPart = part;
                Predicate<MeetingSlot> isOccupyingThisSlot = slot -> {
                    if (slot.getHour() != finalHour)
                        return false;
                    if (slot.getPart() != finalPart)
                        return false;
                    return slot.getDate().isEqual(date);
                };
                if (rawOccupiedSlots.stream().anyMatch(isOccupyingThisSlot))
                    continue;
                Slot.SlotBuilder builder = Slot.builder();
                builder.date(date);
                builder.hour(finalHour);
                builder.segment(finalPart);
                freeSlots.add(builder.build());
            }
        }
        return freeSlots;
    }

    @Override
    public List<Map<String, Object>> getJoinableMeetings() {
        List<Map<String, Object>> result = new ArrayList<>();
        List<Meeting> meetings = getAllMeetings();

        for (Meeting m : meetings) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", m.getId());
            map.put("topic", m.getTopic());
            List<MeetingSlot> occupiedSlots = meetingSlotMapper.selectList(
                    new QueryWrapper<MeetingSlot>().eq("meeting_id", m.getId()));
            if (occupiedSlots.isEmpty()) {
                String exception = String.format(
                        "会议%d(%s)没有占用任何时段", m.getId(), m.getTopic());
                throw new GoldfishBookingException(exception);
            }
            List<Slot> slots = occupiedSlots.stream()
                    .map(ms -> {
                        Slot slot = bookingMapper.fromMeetingSlot(ms);
                        slot.setDate(ms.getDate());
                        return slot;
                    })
                    .toList();
            if (slots.isEmpty()) {
                String exception = String.format(
                        "会议%d(%s)没有占用任何时段", m.getId(), m.getTopic()
                );
                throw new GoldfishBookingException(exception);
            }
            Slot start = slots.stream()
                    .min(Comparator.comparing(Slot::getTime)).orElse(null);
            LocalTime startTime = start.getTime().toLocalTime();
            LocalTime timeNow = LocalTime.now();
            DECIDE_START_TIME:
            {
                if (!start.getDate().isBefore(LocalDate.now()))
                    break DECIDE_START_TIME;
                if (startTime.isBefore(timeNow)) {
                    log.warn("忽略 {}", start);
                    continue;
                }

            }
            Slot end = slots.stream()
                    .max(Comparator.comparing(Slot::getTime)).get();
            map.put("time", start.getTime() + " - " + end.getTime());
            List<MeetingRoom> rooms = meetingRoomMapper.selectList(
                    new QueryWrapper<MeetingRoom>().eq("id", m.getRoomId()));
            if (rooms.isEmpty()) {
                String exception = String.format(
                        "会议%d(%s)没有占用任何会议室",
                        m.getId(), m.getTopic());
                throw new GoldfishBookingException(exception);
            }
            map.put("roomName", rooms.get(0).getRoomName());
            result.add(map);
        }
        return result;
    }

    @Override
    public List<Meeting> getJoinedMeetings(String email) {
        SysUser user = userMapper.selectOne(new QueryWrapper<SysUser>().eq("email", email));
        Long userId = user.getId();
        List<Meeting> meetings = meetingMapper
                .selectList(
                        new QueryWrapper<Meeting>().
                                eq("organizer_id", userId)
                );
        log.debug("{} 已加入的会议： {}", user, meetings);
        return meetings;

    }

}
